/*  This file is part of -_-.

    -_- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    -_- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with -_-.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2012-2013 Oliver Katz */

#include "ipc.h"
#include "../common/debug.h"

z_ipc_server z_ipc_server_init(int port, int domain)
{
	z_ipc_server tmp;
	tmp.domain = domain;
	tmp.listenfd = -1;
	tmp.confd = -1;
	tmp.port = port;
	memset(&tmp.server, 0, sizeof(tmp.server));
	return tmp;
}

z_ipc_server z_ipc_server_init_global(int port)
{
	return z_ipc_server_init(port, AF_INET);
}

z_ipc_server z_ipc_server_init_local(int port)
{
	return z_ipc_server_init(port, AF_UNIX);
}

z_ipc_server *z_ipc_server_new_global(int port)
{
	z_ipc_server *tmp = (z_ipc_server *)malloc(sizeof(z_ipc_server));
	*tmp = z_ipc_server_init_global(port);
	return tmp;
}

z_ipc_server *z_ipc_server_new_local(int port)
{
	z_ipc_server *tmp = (z_ipc_server *)malloc(sizeof(z_ipc_server));
	*tmp = z_ipc_server_init_local(port);
	return tmp;
}

int z_ipc_server_release(z_ipc_server i)
{
	return 0;
}

int z_ipc_server_delete(z_ipc_server *i)
{
	z_ipc_server_release(*i);
	free(i);
	return 0;
}

int z_ipc_server_connect(z_ipc_server *i)
{
	i->listenfd = socket(i->domain, SOCK_STREAM, 0);
	if (i->listenfd < 0)
	{
		Z_WARNING("cannot open a socket (port %i)", i->port);
		Z_NOTE("errno", "%s", strerror(errno));
		return 1;
	}
	i->server.sin_family = i->domain;
	i->server.sin_addr.s_addr = htonl(INADDR_ANY);
	i->server.sin_port = htons(i->port);
	bind(i->listenfd, (struct sockaddr *)&(i->server), sizeof(i->server));
	listen(i->listenfd, 5);
	return 0;
}

int z_ipc_server_disconnect(z_ipc_server *i)
{
	shutdown(i->listenfd, SHUT_RDWR);
	return 0;
}

z_data z_ipc_server_wait(z_ipc_server *i, int *p, char **a)
{
	struct sockaddr_in cliaddr;
	socklen_t l = sizeof(cliaddr);
	i->confd = accept(i->listenfd, (struct sockaddr *)&cliaddr, &l);
	if (i->confd < 0)
	{
		Z_WARNING("cannot accept IPC client (port %i)", i->port);
		Z_NOTE("errno", "%s", strerror(errno));
		return z_data_init(0, 0);
	}

	if (p != NULL)
		*p = cliaddr.sin_port;
	if (a != NULL)
	{
		*a = (char *)malloc(sizeof(char)*INET_ADDRSTRLEN);
		inet_ntop(AF_INET, &(cliaddr.sin_addr), *a, INET_ADDRSTRLEN);
	}

	z_data tmp = z_data_init(0, 0);
	if (read(i->confd, &tmp, sizeof(tmp)) < 0)
	{
		Z_WARNING("cannot read data packet header (port %i)", i->port);
		Z_NOTE("errno", "%s", strerror(errno));
		return tmp;
	}

	if (tmp.size > 0)
	{
		tmp.src = malloc(tmp.size);
		memset(tmp.src, 0, tmp.size);
		if (read(i->confd, tmp.src, tmp.size) < 0)
		{
			Z_WARNING("cannot read data packet contents (port %i, %i bytes)", i->port, tmp.size);
			Z_NOTE("errno", "%s", strerror(errno));
			free(tmp.src);
			tmp.src = NULL;
			return tmp;
		}
		return tmp;
	}
	else
	{
		Z_WARNING("data packet header references empty data packet (port %i)", i->port);
		tmp.src = NULL;
		return tmp;
	}
}

z_data *_z_ipc_server_wait(z_ipc_server *i)
{
	z_data *tmp = (z_data *)malloc(sizeof(z_data));
	*tmp = z_ipc_server_wait(i, NULL, NULL);
	return tmp;
}

int z_ipc_server_reply(z_ipc_server *i, z_data *d)
{
	void *buf = malloc(sizeof(z_data)+d->size);
	memset(buf, 0, sizeof(z_data)+d->size);
	memcpy(buf, d, sizeof(z_data));
	if (d->size > 0)
		memcpy(buf+sizeof(z_data), d->src, d->size);
	write(i->confd, buf, sizeof(z_data)+d->size);
	close(i->confd);
	return 0;
}

int z_ipc_server_reply_empty(z_ipc_server *i)
{
	close(i->confd);
	return 0;
}

z_ipc_client z_ipc_client_init(char *addr, int port, int domain)
{
	z_ipc_client tmp;
	tmp.addr = addr;
	tmp.sock = -1;
	tmp.port = port;
	tmp.domain = domain;
	memset(&tmp.server, 0, sizeof(tmp.server));
	return tmp;
}

z_ipc_client z_ipc_client_init_global(char *addr, int port)
{
	return z_ipc_client_init(addr, port, AF_INET);
}

z_ipc_client z_ipc_client_init_local(char *addr, int port)
{
	return z_ipc_client_init(addr, port, AF_UNIX);
}

z_ipc_client *z_ipc_client_new_global(char *addr, int port)
{
	z_ipc_client *tmp = (z_ipc_client *)malloc(sizeof(z_ipc_client));
	*tmp = z_ipc_client_init_global(addr, port);
	return tmp;
}

z_ipc_client *z_ipc_client_new_local(char *addr, int port)
{
	z_ipc_client *tmp = (z_ipc_client *)malloc(sizeof(z_ipc_client));
	*tmp = z_ipc_client_init_local(addr, port);
	return tmp;
}

int z_ipc_client_release(z_ipc_client i)
{
	return 0;
}

int z_ipc_client_delete(z_ipc_client *i)
{
	z_ipc_client_release(*i);
	free(i);
	return 0;
}

int z_ipc_client_connect(z_ipc_client *i)
{
	i->sock = socket(i->domain, SOCK_STREAM, 0);
	if (i->sock < 0)
	{
		Z_WARNING("cannot open socket (port %i on %s)", i->port, i->addr);
		Z_NOTE("errno", "%s", strerror(errno));
		return 1;
	}
	i->server.sin_family = i->domain;
	i->server.sin_port = htons(i->port);
	if (inet_pton(i->domain, i->addr, &(i->server.sin_addr)) <= 0)
	{
		Z_WARNING("cannot connect to server (port %i on %s)", i->port, i->addr);
		Z_NOTE("errno", "%s", strerror(errno));
		return 2;
	}
	return 0;
}

int z_ipc_client_disconnect(z_ipc_client *i)
{
	shutdown(i->sock, SHUT_RDWR);
	return 0;
}

int z_ipc_client_send_request(z_ipc_client *i, z_data *req)
{
	if (connect(i->sock, (struct sockaddr *)&(i->server), sizeof(i->server)) < 0)
	{
		Z_WARNING("cannot open connection (port %i on %s)", i->port, i->addr);
		Z_NOTE("errno", "%s", strerror(errno));
		return 1;
	}

	void *buf = malloc(sizeof(z_data)+req->size);
	memset(buf, 0, sizeof(z_data)+req->size);
	memcpy(buf, req, sizeof(z_data));
	if (req->size > 0)
		memcpy(buf+sizeof(z_data), req->src, req->size);
	write(i->sock, buf, sizeof(z_data)+req->size);
	return 0;
}

z_data z_ipc_client_recieve_response(z_ipc_client *i)
{
	z_data res = z_data_init(0, 0);
	if (read(i->sock, &res, sizeof(z_data)) < 0)
	{
		Z_WARNING("cannot read data packet header (port %i on %s)", i->port, i->addr);
		Z_NOTE("errno", "%s", strerror(errno));
		return res;
	}

	if (res.size > 0)
	{
		res.src = malloc(res.size);
		memset(res.src, 0, res.size);
		read(i->sock, res.src, res.size);
	}
	close(i->sock);
	return res;
}

z_data *_z_ipc_client_recieve_response(z_ipc_client *i)
{
	z_data *tmp = (z_data *)malloc(sizeof(z_data));
	*tmp = z_ipc_client_recieve_response(i);
	return tmp;
}
